

import http.server
import socketserver
import os
import cgi
import sys
import functools
from http import HTTPStatus
from datetime import datetime
import urllib.parse
import html

# --- 配置 ---
PORT = 8000
# UPLOAD_DIR 的默认值，如果命令行没有提供
DEFAULT_DIR = "uploads"

# --- HTML 模板 ---
# CSS中的花括号需要用双花括号 {{ 和 }} 转义
# 动态内容的占位符使用单花括号 { }
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传和列表服务</title>
    <style>
        body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 20px; background-color: #f8f9fa; color: #333; font-size: 14px; }}
        .container {{ max-width: 900px; margin: 0 auto; background-color: #fff; padding: 10px 15px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }}
        h1 {{ font-size: 1.8em; color: #343a40;text-shadow: 1px 1px gray;}}
        h2 {{ font-size: 1.4em; color: #495057; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-top: 10px; padding-top: 0;}}
        .upload-form {{ margin-bottom: 25px; padding: 15px; border: 1px solid #dee2e6; border-radius: 5px; background-color: #f8f9fa; padding-top: 0; }}
        table {{ width: 100%; border-collapse: collapse; font-size: 0.95em; }}
        th, td {{ padding: 10px 12px; text-align: left; border-bottom: 1px solid #e9ecef; }}
        td.filename-cell {{ max-width: 350px; white-space: normal; word-break: break-all; }}
        th {{ background-color: #f8f9fa; font-weight: 600; color: #495057; }}
        tr:hover {{ background-color: #f1f3f5; }}
        a {{ text-decoration: none; color: #007bff; }}
        a:hover {{ text-decoration: underline; }}
        .footer {{ margin-top: 25px; text-align: center; font-size: 0.85em; color: #6c757d; }}
        input[type="file"] {{ border: 1px solid #ced4da; padding: 6px 8px; border-radius: 4px; width: calc(100% - 100px); font-size: 0.9em; }}
        input[type="submit"], button {{ background-color: #007bff; color: white; padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9em; }}
        input[type="submit"]:hover, button:hover {{ background-color: #0056b3; }}
        button.delete-btn {{ background-color: #dc3545; }}
        button.delete-btn:hover {{ background-color: #c82333; }}
    </style>
    <script>
        function confirmDelete(filename) {{
            // 使用JSON.stringify来正确处理文件名中的引号和特殊字符
            return confirm('确定要删除文件 ' + JSON.stringify(filename) + ' 吗？');
        }}
    </script>
    <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext x='50' y='50' text-anchor='middle' dy='.3em' font-size='70'%3E📂%3C/text%3E%3C/svg%3E">
</head>
<body>
    <div class="container">
        <h1>静态文件服务器</h1>
        
        <div class="upload-form">
            <h2>上传文件</h2>
            <form action="/" method="post" enctype="multipart/form-data">
                <input type="file" name="files" multiple>
                <input type="submit" value="上传">
            </form>
            <p style="margin-top: 8px; color: #6c757d; font-size: 0.85em;">提示：按住 Ctrl（Windows）或 Command（Mac）可选择多个文件</p>
        </div>

        <h2>文件列表</h2>
        <table>
            <thead>
                <tr>
                    <th>#</th>
                    <th>文件名</th>
                    <th>大小</th>
                    <th>修改时间</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                {file_rows}
            </tbody>
        </table>
        
        <div class="footer">
            <p>服务目录绝对路径: <code>{abs_path}</code></p>
        </div>
    </div>
</body>
</html>
"""

def format_size(size: int) -> str:
    """格式化文件大小为可读性强的字符串"""
    if size < 1024:
        return f"{size} B"
    elif size < 1024 ** 2:
        return f"{size / 1024:.2f} KB"
    elif size < 1024 ** 3:
        return f"{size / (1024 ** 2):.2f} MB"
    else:
        return f"{size / (1024 ** 3):.2f} GB"

class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    """
    一个自定义的请求处理器，增加了文件上传、删除和动态HTML页面生成功能。
    这个处理器在启动时通过 functools.partial 绑定到一个特定的目录。
    """
    def do_GET(self):
        """处理 GET 请求，生成主页面"""
        if self.path == '/':
            self.send_response(HTTPStatus.OK)
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            
            file_rows = self.generate_file_table_rows()
            
            # 使用 .format() 填充模板
            response_html = HTML_TEMPLATE.format(file_rows=file_rows, abs_path=html.escape(self.directory))
            self.wfile.write(response_html.encode('utf-8'))
        else:
            # 对于其他路径（如文件下载），使用基类的处理方式
            super().do_GET()

    def do_POST(self):
        """处理 POST 请求，根据表单内容区分上传和删除操作"""
        content_type, pdict = cgi.parse_header(self.headers['Content-Type'])

        if content_type == 'multipart/form-data':
            pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
            form = cgi.FieldStorage(
                fp=self.rfile,
                headers=self.headers,
                environ={'REQUEST_METHOD': 'POST',
                         'CONTENT_TYPE': self.headers['Content-Type'],
                         })
            self.handle_upload(form)
        elif content_type == 'application/x-www-form-urlencoded':
            content_length = int(self.headers['Content-Length'])
            post_data = self.rfile.read(content_length)
            form_data = urllib.parse.parse_qs(post_data.decode('utf-8'))
            self.handle_delete(form_data)
        else:
            self.send_error(HTTPStatus.BAD_REQUEST, "Unsupported POST request type")


    def handle_upload(self, form: cgi.FieldStorage):
        """处理文件上传（支持多文件）"""
        if 'files' not in form:
            self.send_error(HTTPStatus.BAD_REQUEST, "表单中缺少 'files' 字段")
            return

        file_items = form['files']
        if not isinstance(file_items, list):
            file_items = [file_items]

        uploaded_count = 0
        for file_item in file_items:
            if not file_item.filename:
                continue

            filename = os.path.basename(file_item.filename)
            save_path = os.path.join(self.directory, filename)

            with open(save_path, 'wb') as f:
                f.write(file_item.file.read())

            print(f"文件已上传: {save_path}")
            uploaded_count += 1

        if uploaded_count == 0:
            self.send_error(HTTPStatus.BAD_REQUEST, "没有选择有效的文件")
            return

        print(f"共上传了 {uploaded_count} 个文件")
        self.send_response(HTTPStatus.SEE_OTHER)
        self.send_header('Location', '/')
        self.end_headers()

    def handle_delete(self, form_data: dict):
        """处理文件删除"""
        if 'filename' not in form_data or not form_data['filename']:
            self.send_error(HTTPStatus.BAD_REQUEST, "缺少 'filename' 参数")
            return
            
        filename = form_data['filename'][0]
        filename = os.path.basename(filename)
        file_path = os.path.join(self.directory, filename)

        if not os.path.exists(file_path) or not os.path.isfile(file_path):
            self.send_error(HTTPStatus.NOT_FOUND, "文件未找到")
            return

        try:
            os.remove(file_path)
            print(f"文件已删除: {file_path}")
            self.send_response(HTTPStatus.SEE_OTHER)
            self.send_header('Location', '/')
            self.end_headers()
        except OSError as e:
            print(f"删除文件时出错 {file_path}: {e}")
            self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, f"无法删除文件: {e}")

    def generate_file_table_rows(self) -> str:
        """扫描服务目录并生成文件的HTML表格行"""
        try:
            filenames = os.listdir(self.directory)
            if not filenames:
                return '<tr><td colspan="5" style="text-align: center;">当前没有文件。</td></tr>'
            
            file_details = []
            for name in filenames:
                path = os.path.join(self.directory, name)
                if os.path.isfile(path):
                    try:
                        stat = os.stat(path)
                        file_details.append({
                            'name': name,
                            'size': stat.st_size,
                            'mtime': stat.st_mtime
                        })
                    except FileNotFoundError:
                        continue
            
            file_details.sort(key=lambda x: x['mtime'], reverse=True)
            
            rows = []
            for i, details in enumerate(file_details):
                mtime_str = datetime.fromtimestamp(details['mtime']).strftime('%Y-%m-%d %H:%M:%S')
                size_str = format_size(details['size'])
                encoded_name_for_url = urllib.parse.quote(details['name'])
                escaped_name_for_html = html.escape(details['name'])
                escaped_name_for_js = html.escape(details['name']).replace("\"", "&quot;")

                delete_form = f'''
                    <form action="/" method="post" onsubmit="return confirmDelete('{escaped_name_for_js}');" style="display:inline;" enctype="application/x-www-form-urlencoded">
                        <input type="hidden" name="filename" value="{escaped_name_for_html}">
                        <button type="submit" class="delete-btn">删除</button>
                    </form>
                '''
                
                rows.append(
                    f'<tr>'
                    f'<td>{i + 1}</td>'
                    f'<td class="filename-cell"><a href="{encoded_name_for_url}" target="_blank">{escaped_name_for_html}</a></td>'
                    f'<td>{size_str}</td>'
                    f'<td>{mtime_str}</td>'
                    f'<td>{delete_form}</td>'
                    f'</tr>'
                )
            return "\n".join(rows)
        except FileNotFoundError:
            return f'<tr><td colspan="5">错误: 目录 \'{html.escape(self.directory)}\' 不存在。</td></tr>'

def run_server(directory: str):
    """启动服务器"""
    abs_directory = os.path.abspath(directory)
    print(f"服务目录: {abs_directory}")

    os.makedirs(abs_directory, exist_ok=True)
    
    Handler = functools.partial(CustomHTTPRequestHandler, directory=abs_directory)
    
    with socketserver.TCPServer(('', PORT), Handler) as httpd:
        print(f"服务器已启动，正在监听端口 {PORT}")
        print(f"请在浏览器中打开 http://localhost:{PORT}")
        print("使用 Ctrl+C 停止服务器。")
        httpd.serve_forever()

if __name__ == "__main__":
    if len(sys.argv) > 1:
        serve_dir = sys.argv[1]
    else:
        serve_dir = DEFAULT_DIR
    
    run_server(serve_dir)
